﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;
using InterfacesPhoneCommands;
using Microsoft.Xde.Common;
using Microsoft.Xde.Wmi;

namespace VirtualMachines
{
    public class HyperVControl
    {
        private static HyperVControl _currentControl;
        private static readonly object SyncRoot = new object();

        private readonly Exception _criticalError = null;
        private IReadOnlyCollection<IXdeVirtualMachine> _xdeVirtualMachines;

        public event EventHandler<ChangedMachineStatusEventArgs> ChangedMachineStatus;

        private HyperVControl()
        {
            _xdeVirtualMachines = new XdeWmiFactory().GetAllXdeVirtualMachines(SettingsOptions.ReadOnly |SettingsOptions.DontCreateSwitches);
            foreach (var xdeVirtualMachine in _xdeVirtualMachines)
            {
                xdeVirtualMachine.EnableStateChanged += VirtualMachineEnableStateChanged;
            }
        }

        public void CloseXdeVirtualMachines()
        {
            if (_xdeVirtualMachines == null) return;
            foreach (var xdeVirtualMachine in _xdeVirtualMachines)
            {
                xdeVirtualMachine.Dispose();
            }
        }

        public ResponseVirtualMachines Machines(bool reload)
        {
            try
            {
                if (reload || _xdeVirtualMachines == null)
                {
                    CloseXdeVirtualMachines();
                    _xdeVirtualMachines = new XdeWmiFactory().GetAllXdeVirtualMachines(SettingsOptions.ReadOnly | SettingsOptions.DontCreateSwitches);
                    foreach (var xdeVirtualMachine in _xdeVirtualMachines)
                    {
                        xdeVirtualMachine.EnableStateChanged += VirtualMachineEnableStateChanged;
                    }
                }
                var machines = new List<VirtualMachine>();
                foreach (var xdeVirtualMachine in _xdeVirtualMachines)
                {
                    try
                    {
                        machines.Add(new VirtualMachine
                        {
                            Name = xdeVirtualMachine.Name,
                            NumProcessors = xdeVirtualMachine.CurrentSettings.NumProcessors,
                            RamSize = xdeVirtualMachine.CurrentSettings.RamSize,
                            State = (VirtualMachineState)xdeVirtualMachine.EnabledState,
                            Snapshots = xdeVirtualMachine.SnapshotSettings.Select(snp => snp.Name).Distinct().ToArray()
                        });
                    }
                    catch (Exception)
                    {
                        machines.Add(new VirtualMachine
                        {
                            Name = xdeVirtualMachine.Name,
                            NumProcessors = 0,
                            RamSize = 0,
                            State = (VirtualMachineState)xdeVirtualMachine.EnabledState,
                            Snapshots = new string[] {}
                        });
                    }
                }
                return new ResponseVirtualMachines { Successful = true, Value = machines.ToArray() };
            }
            catch (Exception ex)
            {
                return new ResponseVirtualMachines { ErrorMessage = ex.Message };
           }
        }

        private void VirtualMachineEnableStateChanged(object sender, EnabledStateChangedEventArgs e)
        {
            if (ChangedMachineStatus != null)
            {
                var machine = (IXdeVirtualMachine) sender;
                try
                {
                    ChangedMachineStatus(this, new ChangedMachineStatusEventArgs(
                        new VirtualMachine
                        {
                            Name = machine.Name,
                            NumProcessors = machine.CurrentSettings.NumProcessors,
                            RamSize = machine.CurrentSettings.RamSize,
                            State = (VirtualMachineState)e.EnabledState,
                            Snapshots = machine.SnapshotSettings.Select(snp => snp.Name).Distinct().ToArray()
                        }
                        ));
                }
                catch (Exception)
                {
                    ChangedMachineStatus(this, new ChangedMachineStatusEventArgs(
                        new VirtualMachine
                        {
                            Name = machine.Name,
                            NumProcessors = 0,
                            RamSize = 0,
                            State = (VirtualMachineState)e.EnabledState,
                            Snapshots = new string[] {}
                        }
                        ));
                }
            }
        }

        public ResponseVoid CleanUpMachines()
        {
            var errors = new List<string>();
            foreach (var xdeVirtualMachine in _xdeVirtualMachines)
            {
                using (xdeVirtualMachine)
                {
                    var isCurrentUser = xdeVirtualMachine.Name.ToLower().EndsWith("." + Environment.UserName.ToLower());
                    //close all machines
                    if (xdeVirtualMachine.EnabledState != VirtualMachineEnabledState.Disabled)
                    {
                        try
                        {
                            xdeVirtualMachine.Stop();
                        }
                        catch (Exception ex)
                        {
                            errors.Add(string.Format("Failed to stop VM '{0}': {1}", xdeVirtualMachine.Name, ex.Message));
                        }
                    }
                    //delete only user machines
                    if (isCurrentUser)
                    {
                        var createdDiffDisk = xdeVirtualMachine.GetCreatedDiffDisk();
                        try
                        {
                            xdeVirtualMachine.DeleteVirtualMachine();
                        }
                        catch (Exception ex)
                        {
                            errors.Add(string.Format("Failed to delete VM '{0}': {1}", xdeVirtualMachine.Name, ex.Message));
                        }
                        if (!string.IsNullOrEmpty(createdDiffDisk) && File.Exists(createdDiffDisk))
                        {
                            try
                            {
                                File.Delete(createdDiffDisk);
                            }
                            catch (Exception ex)
                            {
                                errors.Add(string.Format("Failed to delete diff disk '{0}': {1}", createdDiffDisk, ex.Message));
                            }
                        }
                    }
                }
            }

            //delete phone InternalSwitch 
            var service = XdeHyperVManagementService.GetService();
            using (service)
            {
                var id = (string)service.GetInternalSwitch()["Name"];
                var virtualizationScope = WmiUtils.GetVirtualizationScope();
                var enumerable = WmiUtils.GetAllInstances(virtualizationScope, "Msvm_EmulatedEthernetPortSettingData").Union(WmiUtils.GetAllInstances(virtualizationScope, "Msvm_SyntheticEthernetPortSettingData"));
                var flag = false;
                foreach (var portSettingData in enumerable)
                {
                    ManagementObject virtualSwitch;
                    ManagementObject switchPort;
                    WmiUtils.GetSwitchForPortSettingData(portSettingData, out virtualSwitch, out switchPort);
                    if (virtualSwitch != null && (string)virtualSwitch["Name"] == id)
                    {
                        flag = true;
                        break;
                    }
                }
                if (!flag)
                {
                    using (var switchV2ByUniqueId = service.FindSwitchV2ByUniqueId(id))
                    {
                        if (switchV2ByUniqueId != null)
                        {
                            try
                            {
                                service.XdeDeleteVirtualSwitch(switchV2ByUniqueId);
                            }
                            catch (Exception ex)
                            {
                                errors.Add(string.Format("Failed to delete internal switch '{0}': {1}", switchV2ByUniqueId["ElementName"], ex.Message));
                            }
                        }
                    }
                }
            }
            Machines(true);
            if (errors.Count == 0)
            {
                return  new ResponseVoid{Successful = true};
            }
            var result = new ResponseVoid{ErrorMessage = string.Empty};
            foreach (var error in errors)
            {
                result.ErrorMessage += error + "\n";
            }
            return result;
        }

        private IXdeVirtualMachine FindMachine(string vmName)
        {
            return _xdeVirtualMachines.FirstOrDefault(item => item.Name == vmName);
        }

        public VirtualMachine FindVirtualMachine(string vmName)
        {
            var machine = _xdeVirtualMachines.FirstOrDefault(item => item.Name == vmName);
            // ReSharper disable PossibleNullReferenceException
            try
            {
                return new VirtualMachine
                {
                    Name = machine.Name,
                    NumProcessors = machine.CurrentSettings.NumProcessors,
                    RamSize = machine.CurrentSettings.RamSize,
                    State = (VirtualMachineState)machine.EnabledState,
                    Snapshots = machine.SnapshotSettings.Select(snp => snp.Name).Distinct().ToArray()
                };
            }
            catch (Exception)
            {
                return null;
            }
            // ReSharper restore PossibleNullReferenceException
        }

        public ResponseVoid CloseMachine(string vmName)
        {
            try
            {
                var vm = FindMachine(vmName);
                vm.Stop();
                return  new ResponseVoid{Successful = true};
            }
            catch (Exception ex)
            {
                return new ResponseVoid { ErrorMessage = ex.Message};
            }
        }

        public ResponseVoid DeleteSnapshot(string vmName, string nameSnapshot)
        {
            try
            {
                var vm = FindMachine(vmName);
                vm.RemoveSnapshot(nameSnapshot);
                return new ResponseVoid { Successful = true };
            }
            catch (Exception ex)
            {
                return new ResponseVoid { ErrorMessage = ex.Message };
            }
        }



        public ResponseVoid CreateSnapshot(string vmName, string nameSnapshot)
        {
            try
            {
                var vm = FindMachine(vmName);
                vm.CreateSnapshot(nameSnapshot);
                return new ResponseVoid { Successful = true };
            }
            catch (Exception ex)
            {
                return new ResponseVoid { ErrorMessage = ex.Message };
            }
        }

        
        #region instance

        public static HyperVControl Instance
        {
            get
            {
                if (_currentControl == null)
                {
                    lock (SyncRoot)
                    {
                        if (_currentControl == null)
                        {
                            // ReSharper disable PossibleMultipleWriteAccessInDoubleCheckLocking
                            _currentControl = new HyperVControl();
                            // ReSharper restore PossibleMultipleWriteAccessInDoubleCheckLocking
                        }
                    }
                }
                return _currentControl;
            }
        }

        #endregion

        public Exception CriticalError
        {
            get { return Instance._criticalError; }
        }

    }
}
